﻿using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using CmdTailCmd;
using regx.Properties;
using regxExtensions;
using System;

namespace regx
{
    [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification="Instantiated via type argument to CmdTail.ExecuteIfValid<>()")]
    class SanityCheckParams : RegxParams, ICmdTailCommand
    {
        public bool IsValid(CmdTailContext ctx)
        { //===================================================================
            // Check for weird results and say something about them

            // ReportErrors() notifies the user about any error combinations--ones that just can't be allowed--and
            // return "true", thus causing this command to be "executed." In most cases, only one command will be 
            // executed, so this would report error conditions and exit.
            if (ReportErrors(ctx)) return true;

            // Now, show any sanity warnings unless they're explicitly blocked.
            if (!NoWarnings) 
            {
                // The ParsingExceptions collection should only contain unknown (and safely ignored) settings, since we
                // didn't tell the command tail processor to suppress exceptions.
                foreach (UnknownSettingException e in (from e in ctx.ParsingExceptions where e is UnknownSettingException select e))
                {
                    DisplayError("Unknown parameter: " + e.SettingName);
                }

                // This function has all the special-case meaningless parameter combinations
                ReportMeaningless(ctx);
            }

            // If we didn't bail out at ReportErrors, return false. We don't want to execute this command, we just wanted to show some feedback
            return false;
        }

        public bool Execute(object o)
        { //===================================================================
            // This command only executes if the sanity check failed and we have to prevent other commands from running
            ErrorLevel el = o as ErrorLevel;
            if (el != null) el.Value = ErrorLevelValue.Error;
            return true;
        }

        private bool ReportErrors(CmdTailContext ctx)
        { //===================================================================
            // Just a helper for readability
            Func<string, bool> Passed = (s) => {return ctx.AssignedTokens.Contains(s);};

            // Look for combinations of parameters we want both to warn about and to halt subsequent execution
            bool Error = false;

            // Must pass some form of regex to match against. If the user wants a blank regex, it should be stated explicitly
            if (!Passed("Pattern")) {DisplayError(Resources.ErrorNoPattern); Error = true;}

            // Can't have /File and /Url in the same request
            if (File.IsNotEmpty() && Url.IsNotEmpty()) {DisplayError(Resources.ErrorFileAndUrl); Error = true;}

            // Lots of options aren't allowed with ECMA regex
            // ECMA syntax supposedly can't be Compiled, you have to pass the Uncompiled flag to regx 
            // Docs are inconsistent whether an ECMA regex can be Compiled. http://msdn.microsoft.com/en-us/library/yd1hzczs.aspx#ECMAScript says you can't
            // compile an ECMA regex, whereas http://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&l=en-US&k=k(System.Text.RegularExpressions.RegexOptions.RightToLeft);k(devlang-csharp);&rd=true
            // explicitly says you can. It seems to work, so we allow it for now.
            // if (Syntax == RegxSyntax.ECMA && !Uncompiled)               {DisplayError(Resources.ErrorECMAUncompiled);               Error = true;}
            if (Syntax == RegxSyntax.ECMA && RightToLeft)               {DisplayError(Resources.ErrorECMARTL);                      Error = true;}
            if (Syntax == RegxSyntax.ECMA && IgnorePatternWhitespace)   {DisplayError(Resources.ErrorECMAIPW);                      Error = true;}
            if (Syntax == RegxSyntax.ECMA && ExplicitCapturesOnly)      {DisplayError(Resources.ErrorECMAExcplicitCapturesOnly);    Error = true;}
            if (Syntax == RegxSyntax.ECMA && CultureInvariant)          {DisplayError(Resources.ErrorECMACultureInvariant);         Error = true;}
            
            // ECMA doesn't have any multiple line options
            //TODO: Allow treating the whole file as one line without /sl or /ml?
            if (Syntax == RegxSyntax.ECMA && (AllInput || Multiline || Singleline)) {DisplayError(Resources.ErrorECMAMultipleLine); Error = true;}

            return Error;
        }

        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="This is a clear case of complexity metrics just making no sense")]
        private void ReportMeaningless(CmdTailContext ctx)
        { //===================================================================
            // Just a helper for readability
            Func<string, bool> Passed = (s) => {return ctx.AssignedTokens.Contains(s);};
            
            // Look for combinations of parameters we want to warn about. Making the bools because they are easier to read in if statements.
            bool Replacing      = ReplacePattern.IsNotEmpty();
            bool OutUnmatched   = Output == OutputSelection.Unmatched;
            bool OutAll         = Output == OutputSelection.All;
            bool FilterSearch   = File.IsEmpty() && Url.IsEmpty();
            bool FileSearch     = File.IsNotEmpty();
            bool UrlSearch      = Url.IsNotEmpty();
            bool ShowFile       = Details == OutputDetails.FileAndLine;

            bool PassedDetail     = Passed("Details");
            bool PassedInvert     = Passed("InvertResults");
            bool PassedOutput     = Passed("Output");
            bool PassedFormat     = Passed("Format");
            bool PassedAllInput   = Passed("AllInput");
            
            if (Replacing     && OutUnmatched)  DisplayError(Resources.WarnReplaceOnlyUnmatched);
            if (Replacing     && InvertResults) DisplayError(Resources.WarnReplaceOnlyUnmatched);

            if (InvertResults && OutUnmatched)  DisplayError(Resources.WarnInvertUnmatched);
            if (InvertResults && OutAll)        DisplayError(Resources.WarnInvertAllLines);

            if (FilterSearch  && ShowFile)      DisplayError(Resources.WarnFilterShowFilename);
            
            if (FilterSearch  && Recurse)       DisplayError(Resources.WarnFilterRecurse);
            if (UrlSearch     && Recurse)       DisplayError(Resources.WarnRecurseUrl);
            
            if (FileSearch    && Fusk)          DisplayError(Resources.WarnFileFusk);
            if (FilterSearch  && Fusk)          DisplayError(Resources.WarnFilterFusk);

            if (PassedFormat  && PassedDetail)  DisplayError(Resources.WarnFormatDetail);
            if (PassedFormat  && PassedInvert)  DisplayError(Resources.WarnFormatInvert);
            if (PassedFormat  && PassedOutput)  DisplayError(Resources.WarnFormatOutput);
        
            // The /all flag is a combination of /sl and /ml. Let's warn them if they don't understand that
            // We don't need to test PassedAllInput because that's the only way to set it to True
            if (AllInput && (Singleline || Multiline)) DisplayError(Resources.WarnAllInputRedundant);

            // /all- is incompatible with /sl or /ml
            if (PassedAllInput && !AllInput && (Singleline || Multiline)) DisplayError(Resources.WarnIncompatibleAllInputFalse);
        }
    }
}
